/*
FileSystem.h
*/

#ifndef FileSystem_h
#define FileSystem_h

#include <string>
#include <map>
#include <iostream>
#include <fstream>
#include <vector>
#include <sstream>

#include <cstdlib>
#include <cstdio>
#include <cstring>

#include "Directory.h"
#include "Settings.h"
#include "Utils.h"

using namespace std;

/*
FileSystem:
---------------
| Superblock
| One byte for each data block
| Byte = '\0' if data block is free, '1' if used
---------------
| Inodes
| Indexed from 0 to NumInodes-1
---------------
| Data blocks
| Indexed from 0 to NumDataBlocks-1
*/

class Cache;
class File;
class DataBlock;

class FileSystem{

    friend class DataBlock; //to write to the file
    friend class File; //to write the inode to the file
    friend class Directory;//to write the inode to the file
    friend class Cache; //to read data blocks from the file

    public:
        FileSystem();
        ~FileSystem();

        /*
        methods for manipulating files:
        */
        int Mkfs();
        int Open(string filename, string flag);
        int Read(unsigned int fd, unsigned int size);
        int Write(unsigned int fd, string str);
        int Seek(unsigned int fd, unsigned int offset);
        int Close(unsigned int fd);

        int MkDir(string str_dir);
        int RmDir(string str_dir);

        int Cd(string str_dir);

        int Link(string src, string dest);
        int UnLink(string filename);

        int Stat(string filename);
        int Ls();

        int Cat(string filename);

        int Cp(string src, string dest);
        void Tree(Directory * cur_dir = NULL, unsigned int indent_lvl = 0);

        int Import(string src_path, string dest_name);
        int Export(string src_name, string dest_path);

    private:
        Directory * create_new_directory(); //creates a new directory in the simulated file system
        File * create_or_get_file(string filename); //creates a new file in the simulated file system (if it doesn't exist). returns the existing file if it exists

        Inode * get_free_inode(unsigned int *inode_id);
        int set_inode_to_free(unsigned int inode_id); //re-initializes all the inode's values to defaults, sets this inode as 'free' so that it can be used for a new file
        Directory * get_dir(string str_dir); //pass a directory (no filename) such as ".", "/", "text/test", etc...
        int split_parent_child(string str_full, string & str_parent, string & str_child); //path is a directory or file path, sets parent to all but the last folder or the filename, child is the last folder or filename
        DataBlock * get_free_data_block();
        int inode_id_from_name(string str_path, unsigned int * inode_id);

        //actually perform i/o on the FILE *
        int write_superblock(); //call this after the superblock array is updated
        int write_inode(File * file);
        int write_inode(unsigned int inode_id, Inode * inode);
        int write(DataBlock * data_block,unsigned int offset, unsigned int size);
        int read(DataBlock * data_block);

        FILE * fs_file;
        char * superblock; //we read the entire superblock into memory. Each char corresponds to a data-block. '1' if used, '\0' if free (so it can be initialized with bzero)
        Inode ** inodes; //hold the inodes in memory

        unsigned int cur_dir_inode_id;
        unsigned int root_dir_inode_id;
        char * buf; //for holding the read buffer
        map < unsigned int, File * > open_files; //key is a file descriptor (fd)
        map < unsigned int, string > open_files_permissions; //key is a file descriptor (fd)

        Cache * cache;
        unsigned int fs_num; //each call to Mkfs creates a new file system whose filename ends in this number
};

#endif
